<?php
//=======================================================================
// File:        JPGRAPH_PLOTBAND.PHP
// Description: PHP4 Graph Plotting library. Extension module.
// Created:     2004-02-18
// Ver:         $Id: jpgraph_plotband.php 1106 2009-02-22 20:16:35Z ljp $
//
// Copyright (c) Asial Corporation. All rights reserved.
//========================================================================

// Constants for types of static bands in plot area
define("BAND_RDIAG",1); // Right diagonal lines
define("BAND_LDIAG",2); // Left diagonal lines
define("BAND_SOLID",3); // Solid one color
define("BAND_VLINE",4); // Vertical lines
define("BAND_HLINE",5);  // Horizontal lines
define("BAND_3DPLANE",6);  // "3D" Plane
define("BAND_HVCROSS",7);  // Vertical/Hor crosses
define("BAND_DIAGCROSS",8); // Diagonal crosses


// Utility class to hold coordinates for a rectangle
class Rectangle {
	public $x,$y,$w,$h;
	public $xe, $ye;
	function __construct($aX,$aY,$aWidth,$aHeight) {
		$this->x=$aX;
		$this->y=$aY;
		$this->w=$aWidth;
		$this->h=$aHeight;
		$this->xe=$aX+$aWidth-1;
		$this->ye=$aY+$aHeight-1;
	}
}

//=====================================================================
// Class RectPattern
// Base class for pattern hierarchi that is used to display patterned
// bands on the graph. Any subclass that doesn't override Stroke()
// must at least implement method DoPattern($aImg) which is responsible
// for drawing the pattern onto the graph.
//=====================================================================
class RectPattern {
	protected $color;
	protected $weight;
	protected $rect=null;
	protected $doframe=true;
	protected $linespacing; // Line spacing in pixels
	protected $iBackgroundColor=-1;  // Default is no background fill

	function __construct($aColor,$aWeight=1) {
		$this->color = $aColor;
		$this->weight = $aWeight;
	}

	function SetBackground($aBackgroundColor) {
		$this->iBackgroundColor=$aBackgroundColor;
	}

	function SetPos($aRect) {
		$this->rect = $aRect;
	}

	function ShowFrame($aShow=true) {
		$this->doframe=$aShow;
	}

	function SetDensity($aDens) {
		if( $aDens < 1 || $aDens > 100 )
		JpGraphError::RaiseL(16001,$aDens);
		//(" Desity for pattern must be between 1 and 100. (You tried $aDens)");
		// 1% corresponds to linespacing=50
		// 100 % corresponds to linespacing 1
		$this->linespacing = floor(((100-$aDens)/100.0)*50)+1;

	}

	function Stroke($aImg) {
		if( $this->rect == null )
		JpGraphError::RaiseL(16002);
		//(" No positions specified for pattern.");

		if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
			$aImg->SetColor($this->iBackgroundColor);
			$aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
		}

		$aImg->SetColor($this->color);
		$aImg->SetLineWeight($this->weight);

		// Virtual function implemented by subclass
		$this->DoPattern($aImg);

		// Frame around the pattern area
		if( $this->doframe )
		$aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
	}

}


//=====================================================================
// Class RectPatternSolid
// Implements a solid band
//=====================================================================
class RectPatternSolid extends RectPattern {

	function __construct($aColor="black",$aWeight=1) {
		parent::__construct($aColor,$aWeight);
	}

	function DoPattern($aImg) {
		$aImg->SetColor($this->color);
		$aImg->FilledRectangle($this->rect->x,$this->rect->y,
		$this->rect->xe,$this->rect->ye);
	}
}

//=====================================================================
// Class RectPatternHor
// Implements horizontal line pattern
//=====================================================================
class RectPatternHor extends RectPattern {

	function __construct($aColor="black",$aWeight=1,$aLineSpacing=7) {
		parent::__construct($aColor,$aWeight);
		$this->linespacing = $aLineSpacing;
	}

	function DoPattern($aImg) {
		$x0 = $this->rect->x;
		$x1 = $this->rect->xe;
		$y = $this->rect->y;
		while( $y < $this->rect->ye ) {
			$aImg->Line($x0,$y,$x1,$y);
			$y += $this->linespacing;
		}
	}
}

//=====================================================================
// Class RectPatternVert
// Implements vertical line pattern
//=====================================================================
class RectPatternVert extends RectPattern {

	function __construct($aColor="black",$aWeight=1,$aLineSpacing=7) {
		parent::__construct($aColor,$aWeight);
		$this->linespacing = $aLineSpacing;
	}

	//--------------------
	// Private methods
	//
	function DoPattern($aImg) {
		$x = $this->rect->x;
		$y0 = $this->rect->y;
		$y1 = $this->rect->ye;
		while( $x < $this->rect->xe ) {
			$aImg->Line($x,$y0,$x,$y1);
			$x += $this->linespacing;
		}
	}
}


//=====================================================================
// Class RectPatternRDiag
// Implements right diagonal pattern
//=====================================================================
class RectPatternRDiag extends RectPattern {

	function __construct($aColor="black",$aWeight=1,$aLineSpacing=12) {
		parent::__construct($aColor,$aWeight);
		$this->linespacing = $aLineSpacing;
	}

	function DoPattern($aImg) {
		//  --------------------
		//  | /   /   /   /   /|
		//  |/   /   /   /   / |
		//  |   /   /   /   /  |
		//  --------------------
		$xe = $this->rect->xe;
		$ye = $this->rect->ye;
		$x0 = $this->rect->x + round($this->linespacing/2);
		$y0 = $this->rect->y;
		$x1 = $this->rect->x;
		$y1 = $this->rect->y + round($this->linespacing/2);

		while($x0<=$xe && $y1<=$ye) {
			$aImg->Line($x0,$y0,$x1,$y1);
			$x0 += $this->linespacing;
			$y1 += $this->linespacing;
		}

		if( $xe-$x1 > $ye-$y0 ) {
			// Width larger than height
			$x1 = $this->rect->x + ($y1-$ye);
			$y1 = $ye;
			$y0 = $this->rect->y;
			while( $x0 <= $xe ) {
				$aImg->Line($x0,$y0,$x1,$y1);
				$x0 += $this->linespacing;
				$x1 += $this->linespacing;
			}
			 
			$y0=$this->rect->y + ($x0-$xe);
			$x0=$xe;
		}
		else {
			// Height larger than width
			$diff = $x0-$xe;
			$y0 = $diff+$this->rect->y;
			$x0 = $xe;
			$x1 = $this->rect->x;
			while( $y1 <= $ye ) {
				$aImg->Line($x0,$y0,$x1,$y1);
				$y1 += $this->linespacing;
				$y0 += $this->linespacing;
			}
			 
			$diff = $y1-$ye;
			$y1 = $ye;
			$x1 = $diff + $this->rect->x;
		}

		while( $y0 <= $ye ) {
			$aImg->Line($x0,$y0,$x1,$y1);
			$y0 += $this->linespacing;
			$x1 += $this->linespacing;
		}
	}
}

//=====================================================================
// Class RectPatternLDiag
// Implements left diagonal pattern
//=====================================================================
class RectPatternLDiag extends RectPattern {

	function __construct($aColor="black",$aWeight=1,$aLineSpacing=12) {
		$this->linespacing = $aLineSpacing;
		parent::__construct($aColor,$aWeight);
	}

	function DoPattern($aImg) {
		//  --------------------
		//  |\   \   \   \   \ |
		//  | \   \   \   \   \|
		//  |  \   \   \   \   |
		//  |------------------|
		$xe = $this->rect->xe;
		$ye = $this->rect->ye;
		$x0 = $this->rect->x + round($this->linespacing/2);
		$y0 = $this->rect->ye;
		$x1 = $this->rect->x;
		$y1 = $this->rect->ye - round($this->linespacing/2);

		while($x0<=$xe && $y1>=$this->rect->y) {
			$aImg->Line($x0,$y0,$x1,$y1);
			$x0 += $this->linespacing;
			$y1 -= $this->linespacing;
		}
		if( $xe-$x1 > $ye-$this->rect->y ) {
			// Width larger than height
			$x1 = $this->rect->x + ($this->rect->y-$y1);
			$y0=$ye; $y1=$this->rect->y;
			while( $x0 <= $xe ) {
				$aImg->Line($x0,$y0,$x1,$y1);
				$x0 += $this->linespacing;
				$x1 += $this->linespacing;
			}
			 
			$y0=$this->rect->ye - ($x0-$xe);
			$x0=$xe;
		}
		else {
			// Height larger than width
			$diff = $x0-$xe;
			$y0 = $ye-$diff;
			$x0 = $xe;
			while( $y1 >= $this->rect->y ) {
				$aImg->Line($x0,$y0,$x1,$y1);
				$y0 -= $this->linespacing;
				$y1 -= $this->linespacing;
			}
			$diff = $this->rect->y - $y1;
			$x1 = $this->rect->x + $diff;
			$y1 = $this->rect->y;
		}
		while( $y0 >= $this->rect->y ) {
			$aImg->Line($x0,$y0,$x1,$y1);
			$y0 -= $this->linespacing;
			$x1 += $this->linespacing;
		}
	}
}

//=====================================================================
// Class RectPattern3DPlane
// Implements "3D" plane pattern
//=====================================================================
class RectPattern3DPlane extends RectPattern {
	private $alpha=50;  // Parameter that specifies the distance
	// to "simulated" horizon in pixel from the
	// top of the band. Specifies how fast the lines
	// converge.

	function __construct($aColor="black",$aWeight=1) {
		parent::__construct($aColor,$aWeight);
		$this->SetDensity(10);  // Slightly larger default
	}

	function SetHorizon($aHorizon) {
		$this->alpha=$aHorizon;
	}

	function DoPattern($aImg) {
		// "Fake" a nice 3D grid-effect.
		$x0 = $this->rect->x + $this->rect->w/2;
		$y0 = $this->rect->y;
		$x1 = $x0;
		$y1 = $this->rect->ye;
		$x0_right = $x0;
		$x1_right = $x1;

		// BTW "apa" means monkey in Swedish but is really a shortform for
		// "alpha+a" which was the labels I used on paper when I derived the
		// geometric to get the 3D perspective right.
		// $apa is the height of the bounding rectangle plus the distance to the
		// artifical horizon (alpha)
		$apa = $this->rect->h + $this->alpha;

		// Three cases and three loops
		// 1) The endpoint of the line ends on the bottom line
		// 2) The endpoint ends on the side
		// 3) Horizontal lines

		// Endpoint falls on bottom line
		$middle=$this->rect->x + $this->rect->w/2;
		$dist=$this->linespacing;
		$factor=$this->alpha /($apa);
		while($x1>$this->rect->x) {
			$aImg->Line($x0,$y0,$x1,$y1);
			$aImg->Line($x0_right,$y0,$x1_right,$y1);
			$x1 = $middle - $dist;
			$x0 = $middle - $dist * $factor;
			$x1_right = $middle + $dist;
			$x0_right =  $middle + $dist * $factor;
			$dist += $this->linespacing;
		}

		// Endpoint falls on sides
		$dist -= $this->linespacing;
		$d=$this->rect->w/2;
		$c = $apa - $d*$apa/$dist;
		while( $x0>$this->rect->x ) {
			$aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
			$aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
			$dist += $this->linespacing;
			$x0 = $middle - $dist * $factor;
			$x1 = $middle - $dist;
			$x0_right =  $middle + $dist * $factor;
			$c = $apa - $d*$apa/$dist;
		}

		// Horizontal lines
		// They need some serious consideration since they are a function
		// of perspective depth (alpha) and density (linespacing)
		$x0=$this->rect->x;
		$x1=$this->rect->xe;
		$y=$this->rect->ye;

		// The first line is drawn directly. Makes the loop below slightly
		// more readable.
		$aImg->Line($x0,$y,$x1,$y);
		$hls = $this->linespacing;

		// A correction factor for vertical "brick" line spacing to account for
		// a) the difference in number of pixels hor vs vert
		// b) visual apperance to make the first layer of "bricks" look more
		// square.
		$vls = $this->linespacing*0.6;

		$ds = $hls*($apa-$vls)/$apa;
		// Get the slope for the "perspective line" going from bottom right
		// corner to top left corner of the "first" brick.

		// Uncomment the following lines if you want to get a visual understanding
		// of what this helpline does. BTW this mimics the way you would get the
		// perspective right when drawing on paper.
		/*
		$x0 = $middle;
		$y0 = $this->rect->ye;
		$len=floor(($this->rect->ye-$this->rect->y)/$vls);
		$x1 = $middle+round($len*$ds);
		$y1 = $this->rect->ye-$len*$vls;
		$aImg->PushColor("red");
		$aImg->Line($x0,$y0,$x1,$y1);
		$aImg->PopColor();
		*/

		$y -= $vls;
		$k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
		$dist = $hls;
		while( $y>$this->rect->y ) {
			$aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
			$adj = $k*$dist/(1+$dist*$k/$apa);
			if( $adj < 2 ) $adj=1;
			$y = $this->rect->ye - round($adj);
			$dist += $hls;
		}
	}
}

//=====================================================================
// Class RectPatternCross
// Vert/Hor crosses
//=====================================================================
class RectPatternCross extends RectPattern {
	private $vert=null;
	private $hor=null;
	function __construct($aColor="black",$aWeight=1) {
		parent::__construct($aColor,$aWeight);
		$this->vert = new RectPatternVert($aColor,$aWeight);
		$this->hor  = new RectPatternHor($aColor,$aWeight);
	}

	function SetOrder($aDepth) {
		$this->vert->SetOrder($aDepth);
		$this->hor->SetOrder($aDepth);
	}

	function SetPos($aRect) {
		parent::SetPos($aRect);
		$this->vert->SetPos($aRect);
		$this->hor->SetPos($aRect);
	}

	function SetDensity($aDens) {
		$this->vert->SetDensity($aDens);
		$this->hor->SetDensity($aDens);
	}

	function DoPattern($aImg) {
		$this->vert->DoPattern($aImg);
		$this->hor->DoPattern($aImg);
	}
}

//=====================================================================
// Class RectPatternDiagCross
// Vert/Hor crosses
//=====================================================================

class RectPatternDiagCross extends RectPattern {
	private $left=null;
	private $right=null;
	function __construct($aColor="black",$aWeight=1) {
		parent::__construct($aColor,$aWeight);
		$this->right = new RectPatternRDiag($aColor,$aWeight);
		$this->left  = new RectPatternLDiag($aColor,$aWeight);
	}

	function SetOrder($aDepth) {
		$this->left->SetOrder($aDepth);
		$this->right->SetOrder($aDepth);
	}

	function SetPos($aRect) {
		parent::SetPos($aRect);
		$this->left->SetPos($aRect);
		$this->right->SetPos($aRect);
	}

	function SetDensity($aDens) {
		$this->left->SetDensity($aDens);
		$this->right->SetDensity($aDens);
	}

	function DoPattern($aImg) {
		$this->left->DoPattern($aImg);
		$this->right->DoPattern($aImg);
	}

}

//=====================================================================
// Class RectPatternFactory
// Factory class for rectangular pattern
//=====================================================================
class RectPatternFactory {
	function __construct() {
		// Empty
	}
	function Create($aPattern,$aColor,$aWeight=1) {
		switch($aPattern) {
			case BAND_RDIAG:
				$obj =  new RectPatternRDiag($aColor,$aWeight);
				break;
			case BAND_LDIAG:
				$obj =  new RectPatternLDiag($aColor,$aWeight);
				break;
			case BAND_SOLID:
				$obj =  new RectPatternSolid($aColor,$aWeight);
				break;
			case BAND_VLINE:
				$obj =  new RectPatternVert($aColor,$aWeight);
				break;
			case BAND_HLINE:
				$obj =  new RectPatternHor($aColor,$aWeight);
				break;
			case BAND_3DPLANE:
				$obj =  new RectPattern3DPlane($aColor,$aWeight);
				break;
			case BAND_HVCROSS:
				$obj =  new RectPatternCross($aColor,$aWeight);
				break;
			case BAND_DIAGCROSS:
				$obj =  new RectPatternDiagCross($aColor,$aWeight);
				break;
			default:
				JpGraphError::RaiseL(16003,$aPattern);
				//(" Unknown pattern specification ($aPattern)");
		}
		return $obj;
	}
}


//=====================================================================
// Class PlotBand
// Factory class which is used by the client.
// It is responsible for factoring the corresponding pattern
// concrete class.
//=====================================================================
class PlotBand {
	public $depth; // Determine if band should be over or under the plots
	private $prect=null;
	private $dir, $min, $max;

	function __construct($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
		$f =  new RectPatternFactory();
		$this->prect = $f->Create($aPattern,$aColor,$aWeight);
		if( is_numeric($aMin) && is_numeric($aMax) && ($aMin > $aMax) )
		JpGraphError::RaiseL(16004);
		//('Min value for plotband is larger than specified max value. Please correct.');
		$this->dir = $aDir;
		$this->min = $aMin;
		$this->max = $aMax;
		$this->depth=$aDepth;
	}

	// Set position. aRect contains absolute image coordinates
	function SetPos($aRect) {
		assert( $this->prect != null ) ;
		$this->prect->SetPos($aRect);
	}

	function ShowFrame($aFlag=true) {
		$this->prect->ShowFrame($aFlag);
	}

	// Set z-order. In front of pplot or in the back
	function SetOrder($aDepth) {
		$this->depth=$aDepth;
	}

	function SetDensity($aDens) {
		$this->prect->SetDensity($aDens);
	}

	function GetDir() {
		return $this->dir;
	}

	function GetMin() {
		return $this->min;
	}

	function GetMax() {
		return $this->max;
	}

	function PreStrokeAdjust($aGraph) {
		// Nothing to do
	}

	// Display band
	function Stroke($aImg,$aXScale,$aYScale) {
		assert( $this->prect != null ) ;
		if( $this->dir == HORIZONTAL ) {
			if( $this->min === 'min' ) $this->min = $aYScale->GetMinVal();
			if( $this->max === 'max' ) $this->max = $aYScale->GetMaxVal();

			// Only draw the bar if it actually appears in the range
			if ($this->min < $aYScale->GetMaxVal() && $this->max > $aYScale->GetMinVal()) {
				 
				// Trucate to limit of axis
				$this->min = max($this->min, $aYScale->GetMinVal());
				$this->max = min($this->max, $aYScale->GetMaxVal());

				$x=$aXScale->scale_abs[0];
				$y=$aYScale->Translate($this->max);
				$width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
				$height=abs($y-$aYScale->Translate($this->min))+1;
				$this->prect->SetPos(new Rectangle($x,$y,$width,$height));
				$this->prect->Stroke($aImg);
			}
		}
		else { // VERTICAL
			if( $this->min === 'min' ) $this->min = $aXScale->GetMinVal();
			if( $this->max === 'max' ) $this->max = $aXScale->GetMaxVal();

			// Only draw the bar if it actually appears in the range
			if ($this->min < $aXScale->GetMaxVal() && $this->max > $aXScale->GetMinVal()) {
				 
				// Trucate to limit of axis
				$this->min = max($this->min, $aXScale->GetMinVal());
				$this->max = min($this->max, $aXScale->GetMaxVal());

				$y=$aYScale->scale_abs[1];
				$x=$aXScale->Translate($this->min);
				$height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
				$width=abs($x-$aXScale->Translate($this->max));
				$this->prect->SetPos(new Rectangle($x,$y,$width,$height));
				$this->prect->Stroke($aImg);
			}
		}
	}
}


?>
